﻿using System;
using System.Linq;
using System.Reflection;
using Microsoft.SharePoint;
using SharePoint.Orm.Attributes;
using System.IO;

namespace SharePoint.Orm.Helpers {
	/// <summary>
	/// Collection of methods for working with <see cref="SPListItem"/>s.
	/// </summary>
	public static class SPListItemHelper {
		/// <summary>
		/// Gets a value from a SPListItem.
		/// </summary>
		/// <typeparam name="T">The type to return.</typeparam>
		/// <param name="listItem">The list item to get the value from.</param>
		/// <param name="ctx">The <see cref="SPDataContext"/>.</param>
		/// <param name="id">The id of the field. This is a GUID string, because this is called from a proxy the method signature has been simplified.</param>
		/// <param name="staticName">The static name of the field. If the id fails this will be used as a fallback.</param>
		/// <param name="title">The title of the field. If the id and the staticName fails this will be used as a fallback.</param>
		/// <returns>The value.</returns>
		public static T GetItemValue<T>(SPListItem listItem, SPDataContext ctx, string id, string staticName, string title) {
			if (listItem == null) {
				throw new ArgumentNullException("listItem", "'listItem' cannot be null.");
			}
			SPOrmFieldAttribute fieldAttribute = new SPOrmFieldAttribute {
				Id = id,
				StaticName = staticName,
				Title = title
			};
			SPField field = FindFieldBySPOrmFieldAttributeOrDefault(listItem, fieldAttribute);
			if (field == null) {
				throw new SPOrmException("Could not find field '" + fieldAttribute.Title + "'");
			}
			Guid fieldId = field.Id;
			object val = listItem[fieldId];
			return ctx.ValueTypeConverter.FromListItemValue<T>(ctx, listItem, fieldId, val);
		}

		/// <summary>
		/// Sets a value on a SPListItem.
		/// </summary>
		/// <typeparam name="T">The type to return.</typeparam>
		/// <param name="listItem">The list item to get the value from.</param>
		/// <param name="ctx">The <see cref="SPDataContext"/>.</param>
		/// <param name="id">The id of the field. This is a GUID string, because this is called from a proxy the method signature has been simplified.</param>
		/// <param name="staticName">The static name of the field. If the id fails this will be used as a fallback.</param>
		/// <param name="title">The title of the field. If the id and the staticName fails this will be used as a fallback.</param>
		/// <param name="value">The value to set.</param>
		/// <returns>The value.</returns>
		public static void SetItemValue<T>(SPListItem listItem, SPDataContext ctx, string id, string staticName, string title, T value) {
			if (listItem == null) {
				throw new ArgumentNullException("listItem", "'listItem' cannot be null.");
			}
			SPOrmFieldAttribute fieldAttribute = new SPOrmFieldAttribute {
				Id = id,
				StaticName = staticName,
				Title = title
			};
			SPField field = FindFieldBySPOrmFieldAttributeOrDefault(listItem, fieldAttribute);
			if (field == null) {
				throw new SPOrmException("Could not find field '" + fieldAttribute.Title + "'");
			}
			Guid fieldId = field.Id;
			listItem[fieldId] = ctx.ValueTypeConverter.ToListItemValue(ctx, listItem, fieldId, value);
		}

		public static SPField FindFieldBySPOrmFieldAttributeOrDefault(SPListItem listItem, SPOrmFieldAttribute fieldAttribute) {
			SPField field = listItem.Fields.Cast<SPField>()
				.FirstOrDefault(f => {
					Guid id;
					if (GuidHelper.TryParse(fieldAttribute.Id, out id)) {
						return f.Id == id;
					}
					return false;
				});
			if (field != null) {
				return field;
			}
			field = listItem.Fields.Cast<SPField>()
				.FirstOrDefault(f => string.Equals(f.StaticName, fieldAttribute.StaticName, StringComparison.InvariantCultureIgnoreCase));
			if (field != null) {
				return field;
			}
			field = listItem.Fields.Cast<SPField>()
				.FirstOrDefault(f => string.Equals(f.Title, fieldAttribute.Title, StringComparison.InvariantCultureIgnoreCase));
			return field;
		}

		public static Stream OpenBinaryStream(SPListItem listItem, SPDataContext ctx) {
			return listItem.File.OpenBinaryStream();
		}

		public static void PopulateFromDecoratedClass(SPListItem listItem, SPDataContext ctx, object obj) {
			PropertyInfo[] propertyInfos = obj.GetType().GetProperties();
			foreach (PropertyInfo propertyInfo in propertyInfos) {
				if (propertyInfo.CanRead) {
					SPOrmFieldAttribute fieldAttr =
						(SPOrmFieldAttribute)propertyInfo.GetCustomAttributes(typeof(SPOrmFieldAttribute), true).FirstOrDefault();
					if (fieldAttr != null) {
						object propertyValue = propertyInfo.GetValue(obj, null);
						SetItemValue(listItem, ctx, fieldAttr.Id, fieldAttr.StaticName, fieldAttr.Title, propertyValue);
					}
				}
			}
		}

		public static void FindOrAddRoleAssignment(SPListItem item, SPWeb site, SPPrincipal user, string[] roleDefinitions) {
			FindOrAddRoleAssignment(item, site, new SPRoleAssignment(user), roleDefinitions);
		}

		public static void FindOrAddRoleAssignment(SPListItem item, SPWeb site, SPRoleAssignment roleAssignment, string[] roleDefinitions) {
			// site is required because the SPListItem might not be attached to a list yet.
			if (!item.HasUniqueRoleAssignments) {
				item.BreakRoleInheritance(false);
				item.SystemUpdate(false);
			}
			foreach (string roleDefinition in roleDefinitions) {
				roleAssignment.RoleDefinitionBindings.Add(site.RoleDefinitions[roleDefinition]);
			}
			item.RoleAssignments.Add(roleAssignment);
			item.SystemUpdate(false);
		}
	}
}
